RustのValidator Libraryのgardeを使ってみる
Introduction
プログラムを書くときに必ずといっていいほど実装する
処理の1つにバリデーションがあります。
バリデーションは入力データが特定の条件を満たしているかどうか
チェックするプロセスです。
この処理はソフトウェアの安全性を保つために非常に重要です。
本稿ではRustのValidationライブラリである
garde(axumとも統合されてるヤツ)を使ってValidationを実装してみます。
Environment
- Rust : 1.70.0
Setup
CargoでRustプロジェクトを作成し、
gardeクレートをインストールします。
% cargo new garde_example && cd garde_example % cargo add garde
Try
gardeでは構造体やEnumにattributeを付与することで
Validationルールを定義します。
基本的な使い方
garde::Validateを使用して、構造体にValidateルールを追加します。
#[derive(Validate)] struct User<'a> { #[garde(required)] id: Option<String>, #[garde(ascii, length(min=3, max=25))] name: &'a str, #[garde(length(min=20))] pass: &'a str, #[garde(email)] mail: &'a str, #[garde(skip)] memo:&'a str, }
User型変数を使ってみます。
validate関数を実行することでValidation処理が行われます。
let user = User { id:Some("ID0001".to_string()), name: "a", pass: "mypassword", mail:"hoge", foo:"hello" }; if let Err(e) = user.validate(&()) { println!("invalid user:\n{e}"); }
mailやlengthのチェックがちゃんとされてます。
% cargo run invalid user: value.mail: not a valid email: value is missing `@` value.name: length is lower than 3 value.pass: length is lower than 20
なお、構造体だけでなくEnumを対象にすると下記のようになります。
#[derive(Validate)] enum Data { Struct { #[garde(range(min=-1, max=10))] field: i32, }, Tuple( #[garde(ascii)] String ), } ・ ・ ・ let data = Data::Struct { field: 100 }; if let Err(e) = data.validate(&()) { println!("invalid data: {e}"); }
なお、Validateionルールにrequieredが付与していた場合、
それ以外のルールはSome型だった場合に検証されます。
gardeが標準で用意している検証ルールは下記になります。
name | format | validation | feature flag |
---|---|---|---|
required | #[garde(required)] | is value set | - |
ascii | #[garde(ascii)] | only contains ASCII | - |
alphanumeric | #[garde(alphanumeric)] | only letters and digits | - |
#[garde(email)] | an email according to the HTML5 spec1 | ||
url | #[garde(url)] | a URL | url |
ip | #[garde(ip)] | an IP address (either IPv4 or IPv6) | - |
ipv4 | #[garde(ipv4)] | an IPv4 address | - |
ipv6 | #[garde(ipv6)] | an IPv6 address | - |
credit card | #[garde(credit_card)] | a credit card number | credit-card |
phone number | #[garde(phone_number)] | a phone number | phone-number |
length | #[garde(length(min=<usize>, max=<usize>))] | a container with length in min..=max | - |
byte_length | #[garde(byte_length(min=<usize>, max=<usize>))] | a byte sequence with length in min..=max | - |
range | #[garde(range(min=<expr>, max=<expr>))] | a number in the range min..=max | - |
contains | #[garde(contains(<string>))] | a string-like value containing a substring | - |
prefix | #[garde(prefix(<string>))] | a string-like value prefixed by some string | - |
suffix | #[garde(suffix(<string>))] | a string-like value suffixed by some string | - |
pattern | #[garde(pattern("<regex>"))] | a string-like value matching some regex | regex |
pattern | #[garde(pattern(<matcher>))] | a string-like value matched by some Matcher | - |
dive | #[garde(dive)] | nested validation, calls validate on the value | - |
skip | #[garde(skip)] | skip validation | - |
custom | #[garde(custom(<function or closure>))] | a custom validator | - |
カスタムバリデーション
バリデーションは自由にカスタマイズすることができます。
指定したフィールドも文字列が任意のVec<String>に含まれるか
チェックするValidationを定義してみます。
#[derive(garde::Validate)] #[garde(context(MapContext))] struct MyLang { #[garde(custom(is_langs))] lang: String, } struct MapContext { langs: Vec<String>, } fn is_langs(value: &str, context: &MapContext) -> garde::Result { if context.langs.contains(&value.to_string()) { println!("The value is present in the Vec!"); } else { println!("The value is not present in the Vec."); return Err(garde::Error::new(format!("{} {}", value, "is not present in the Vec."))); } Ok(()) }
validateにContextを渡し、自作のValidateion関数(is_langs)でチェックします。
let ctx = MapContext { langs: vec![ "rust".to_string(), "java".to_string(), "javascript".to_string(), ], }; let mylang = MyLang { lang : "java".to_string() }; if let Err(e) = mylang.validate(&ctx){ println!("invalid data: {e}"); }
Implementing Validate
ネストされたバリデーションをサポートしたいコンテナ型がある場合、
#[garde(dive)]を使ってValidationを実装できます。
#[repr(transparent)] #[derive(Debug)] struct MyVec<T>(Vec<T>); impl<T: garde::Validate + std::fmt::Debug> garde::Validate for MyVec<T> { type Context = T::Context; fn validate(&self, ctx: &Self::Context) -> Result<(), garde::Errors> { garde::Errors::list(|errors| { for item in self.0.iter() { errors.push(item.validate(ctx)); } }) .finish() } } #[derive(Debug,garde::Validate)] struct Foo { #[garde(dive)] field: MyVec<Bar>, } #[derive(Debug,garde::Validate)] struct Bar { #[garde(range(min = 1, max = 10))] value: u32, }
Summary
今回はRustのValidateionライブラリgardeを使ってみました。
シンプルでカスタマイズも簡単なのですぐに使えると思います。